﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Globalization;
using System.Collections;
using System.Diagnostics;

namespace Serna
{
    /// <summary>
    /// PROTOTYPE NAME SERNA
    /// </summary>
    public static partial class Serializers
    {
               
        static Type TypeDateTime = typeof(DateTime);
        static Type TypeDateTime_NULL = typeof(DateTime?);
        static Type TypeString = typeof(System.String);
        static Type CharType = typeof(System.Char);

        /// <summary>
        /// 
        /// </summary>
        internal class InternalSerializer
        {
            SerializationSettings ss = new SerializationSettings();           

            public string Serialize(object obj, SerializationSettings settings)
            {
                ss = settings;
                StringBuilder attribute = new StringBuilder();
                return SerializeObject(obj, attribute);
            }
            /// <summary>
            /// 
            /// </summary>
            /// <param name="obj"></param>
            /// <returns></returns>
            string SerializeObject(object obj, StringBuilder attribute)
            {
                //Must be supplied from the class


               // bool skipUnnecesarySymbols = false;

                var od = ObjectsFactory.GetObjectDefinition(obj);
                object ho = null;   //help object           
                object key = null;
                object val = null;

                StringBuilder sb = new StringBuilder();
                //attribute.Clear();
                sb.Append("{");
                string internalObject = "";
                StringBuilder myAttributes = null;
                bool encounteredObject = false;


                switch (od.Description)
                {
                    case ObjectDefinition.eObjectDescription.CustomClass:

                        #region "CUSTOM CLASS"

                        foreach (var property in od.Properties)
                        {
                            if (encounteredObject)
                                sb.Append(ss.Delimiter);

                            encounteredObject = true;

                            //Writing key (key is property name (string))
                            WriteToString(sb, TypeString, property.Key, null, ss);

                            //here : can be skipped in short format for space economy
                            if (!this.ss.StringSerialization_SkipUnnecessarySmbols)
                                sb.Append(":");

                            //Getting object
                            ho = property.Value.Getter(obj);

                            if (ho != null)
                            {
                                if (property.Value.TypeConverterToString != null)
                                {
                                    //Primitive type   
                                    WriteToString(sb, property.Value.Type, ho, property.Value.TypeConverterToString, ss);
                                }
                                else
                                {
                                    //Complex type                                   
                                    myAttributes = new StringBuilder();
                                    internalObject = SerializeObject(ho, myAttributes);
                                    if (myAttributes != null && myAttributes.Length > 0)
                                    {
                                        sb.Append(myAttributes.ToString());
                                        if (!this.ss.StringSerialization_SkipUnnecessarySmbols)
                                            sb.Append(":");
                                    }
                                    sb.Append(ss.Framing);
                                    sb.Append(internalObject);
                                    sb.Append(ss.Framing);
                                }

                                ////here ; can be skipped for short format
                                ////if (!skipUnnecesarySymbols)
                                //sb.Append(";");
                            }
                            else
                            {
                                //here ; is NECESSARY, because of NULL value termination
                                //sb.Append(";");
                            }
                        }
                        #endregion

                        break;
                    case ObjectDefinition.eObjectDescription.Dictionary:

                        #region "DICTIONARY"

                        foreach (var el in (IDictionary)obj)
                        {
                            if (encounteredObject)
                                sb.Append(ss.Delimiter);

                            encounteredObject = true;

                            key = ((DictionaryEntry)el).Key;
                            val = ((DictionaryEntry)el).Value;

                            //Writing key
                            WriteToString(sb, od.GenericArgument1, key, od.keyTypeToStringConverter, ss);

                            //here : can be skipped for short format for space economy
                            if (!this.ss.StringSerialization_SkipUnnecessarySmbols)
                                sb.Append(":");

                            if (val != null)
                            {
                                if (od.valueTypeToStringConverter == null)
                                {

                                    //Here we got complex object
                                    myAttributes = new StringBuilder();
                                    internalObject = SerializeObject(val, myAttributes);
                                    if (myAttributes != null && myAttributes.Length > 0)
                                    {
                                        sb.Append(myAttributes.ToString());
                                        if (!this.ss.StringSerialization_SkipUnnecessarySmbols)
                                            sb.Append(":");
                                    }

                                    sb.Append(ss.Framing);
                                    sb.Append(internalObject);
                                    sb.Append(ss.Framing);
                                }
                                else
                                {
                                    WriteToString(sb, od.GenericArgument2, val, od.valueTypeToStringConverter, ss);
                                    //converterValue(sb, val);
                                }

                                ////here ; can be skipped for short format
                                ////if (!skipUnnecesarySymbols)

                                //sb.Append(";");
                            }
                            else
                            {
                                ////here ; is NECESSARY, because of NULL value termination
                                //sb.Append(";");
                            }
                        }

                        #endregion

                        break;

                    case ObjectDefinition.eObjectDescription.List:

                        #region "LIST"

                        foreach (var val1 in (IEnumerable)obj)
                        {

                            ////Empty key 
                            //sb.Append("'");
                            //sb.Append("'");

                            ////here : can be skipped for short format for space economy
                            //if (!this.ss.StringSerialization_SkipUnnecessarySmbols)
                            //    sb.Append(":");

                            if (val1 != null)
                            {
                                if (od.valueTypeToStringConverter == null)
                                {

                                    //Here we got complex object
                                    myAttributes = new StringBuilder();
                                    internalObject = SerializeObject(val1, myAttributes);
                                    if (myAttributes != null && myAttributes.Length > 0)
                                    {
                                        sb.Append(myAttributes.ToString());
                                        if (!this.ss.StringSerialization_SkipUnnecessarySmbols)
                                            sb.Append(":");
                                    }
                                    sb.Append(ss.Framing);
                                    sb.Append(internalObject);
                                    sb.Append(ss.Framing);
                                }
                                else
                                {
                                    WriteToString(sb, od.GenericArgument1, val1, od.valueTypeToStringConverter, ss);
                                }

                                //here ; can be skipped for short format
                                //if (!skipUnnecesarySymbols)
                                sb.Append(";");
                            }
                            else
                            {
                                //here ; is NECESSARY, because of NULL value termination
                                sb.Append(";");
                            }
                        }

                        #endregion

                        break;
                    case ObjectDefinition.eObjectDescription.HashSet:

                        #region "HashSet"


                        foreach (var val1 in (IEnumerable)obj)
                        {
                            ////Empty key 
                            //sb.Append("'");
                            //sb.Append("'");

                            ////here : can be skipped for short format for space economy
                            //if (!this.ss.StringSerialization_SkipUnnecessarySmbols)
                            //    sb.Append(":");

                            if (val1 != null)
                            {
                                if (od.valueTypeToStringConverter == null)
                                {

                                    //Here we got complex object
                                    myAttributes = new StringBuilder();
                                    internalObject = SerializeObject(val1, myAttributes);
                                    if (myAttributes != null && myAttributes.Length > 0)
                                    {
                                        sb.Append(myAttributes.ToString());
                                        if (!this.ss.StringSerialization_SkipUnnecessarySmbols)
                                            sb.Append(":");
                                    }
                                    sb.Append(ss.Framing);
                                    sb.Append(internalObject);
                                    sb.Append(ss.Framing);
                                }
                                else
                                {
                                    WriteToString(sb, od.GenericArgument1, val1, od.valueTypeToStringConverter, ss);
                                }

                                //here ; can be skipped for short format
                                //if (!skipUnnecesarySymbols)
                                sb.Append(";");
                            }
                            else
                            {
                                //here ; is NECESSARY, because of NULL value termination
                                sb.Append(";");
                            }
                        }

                        #endregion

                        break;
                    case ObjectDefinition.eObjectDescription.Array:

                        #region ARRAY

                        //Filling attributes for the array
                        Array ar = (Array)obj;
                        attribute.Append("[");
                        for (int i = 0; i < ar.Rank; i++)
                        {
                            attribute.Append(ar.GetUpperBound(i));
                            if (i != ar.Rank - 1)
                                attribute.Append(" ");
                        }
                        attribute.Append("]");

                        foreach (var val1 in ar)
                        {

                            ////Empty key 
                            //sb.Append("'");
                            //sb.Append("'");

                            ////here : can be skipped for short format for space economy
                            //if (!this.ss.StringSerialization_SkipUnnecessarySmbols)
                            //    sb.Append(":");

                            if (val1 != null)
                            {
                                if (od.valueTypeToStringConverter == null)
                                {

                                    //Here we got complex object
                                    myAttributes = new StringBuilder();
                                    internalObject = SerializeObject(val1, myAttributes);
                                    if (myAttributes != null && myAttributes.Length > 0)
                                    {
                                        sb.Append(myAttributes.ToString());
                                        if (!this.ss.StringSerialization_SkipUnnecessarySmbols)
                                            sb.Append(":");
                                    }
                                    sb.Append(ss.Framing);
                                    sb.Append(internalObject);
                                    sb.Append(ss.Framing);
                                }
                                else
                                {
                                    WriteToString(sb, od.GenericArgument1, val1, od.valueTypeToStringConverter, ss);
                                }

                                //here ; can be skipped for short format
                                //if (!skipUnnecesarySymbols)
                                sb.Append(";");
                            }
                            else
                            {
                                //here ; is NECESSARY, because of NULL value termination
                                sb.Append(";");
                            }
                        }

                        #endregion

                        break;
                }

                sb.Append("}");

                return sb.ToString();
            }


            /// <summary>
            /// 
            /// </summary>
            /// <param name="sb"></param>
            /// <param name="t"></param>
            /// <param name="value"></param>
            /// <param name="?"></param>
            static void WriteToString(StringBuilder sb, Type t, object value, Action<StringBuilder, object> converter, SerializationSettings settings)
            {
                if (value == null)
                    return;

                if (t == TypeDateTime || t == TypeDateTime_NULL)        //Very specific converter, moved out from the others
                {
                    //DateTime (here never null)
                    var dt = (DateTime)value;

                    sb.Append(settings.Framing);
                    switch (settings.DateTimeFormat)
                    {
                        case SerializationSettings.eDateTimeFormat.EuropeanVisual:
                            switch (dt.Kind)
                            {
                                case DateTimeKind.Utc:
                                    //sb.Append(dt.ToString("dd.MM.yyyy HH:mm:ss UTC+0"));
                                    sb.Append(dt.ToString("dd.MM.yyyy HH:mm:ss"));
                                    break;
                                default:

                                    sb.Append(dt.ToString("dd.MM.yyyy HH:mm:ss UTC"));      //'dd.MM.yyyy HH:mm:ss UTC+2' or 'dd.MM.yyyy HH:mm:ss UTC-4'  For Hamburg UTC+1 (Always WinterTime) (Where Sommer Time, DaylightSavingTime = true, added +1 hour in compare with Winter Time)
                                    var hrs = (dt - dt.ToUniversalTime()).TotalHours;
                                    if (dt.IsDaylightSavingTime())
                                        hrs -= 1;   //Bringing to Winter Time
                                    if (hrs >= 0)
                                        sb.Append("+");
                                    sb.Append(hrs);
                                    break;
                            }

                            break;
                        case SerializationSettings.eDateTimeFormat.Universal:
                            switch (dt.Kind)
                            {
                                case DateTimeKind.Utc:
                                    //sb.Append(dt.ToString("yyyyMMdd HHmmss+0"));
                                    sb.Append(dt.ToString("yyyyMMddHHmmss"));
                                    break;
                                default:
                                    sb.Append(dt.ToString("yyyyMMddHHmmss"));
                                    var hrs = (dt - dt.ToUniversalTime()).TotalHours;
                                    if (dt.IsDaylightSavingTime())
                                        hrs -= 1;   //Bringing to Winter Time
                                    if (hrs >= 0)
                                        sb.Append("+");
                                    sb.Append(hrs);
                                    break;
                            }
                            break;
                        case SerializationSettings.eDateTimeFormat.Ticks:
                            sb.Append(dt.Ticks.ToString());
                            break;
                    }
                    sb.Append(settings.Framing);
                }
                else
                {

                    if (converter == null)
                    {
                        //Writing as it is probably Dictionary key
                        sb.Append(settings.Framing);
                        sb.Append(value);
                        sb.Append(settings.Framing);
                    }
                    else
                    {
                        sb.Append(settings.Framing);
                        converter(sb, value);
                        sb.Append(settings.Framing);
                    }
                }
            }

        }

       

        class InternalDeserializer
        {
            SerializationSettings ss = new SerializationSettings();

            public object Deserialize(Type type, string str, SerializationSettings settings)
            {
                ss = settings;
                var od = ObjectsFactory.GetObjectDefinition(type);
                object instance = od.InstanceCreator();
                ObjectDeserializatorFromString op = new ObjectDeserializatorFromString(str);
                op.SerializationSettings = ss;
                op.Obj = instance;
                op.Od = od;
                op.Start(0,"initCaller");

                return instance;
            }


            /// <summary>
            /// 
            /// </summary>
            class ObjectDeserializatorFromString
            {
                enum eState
                {
                    BEGIN,
                    KEY,
                    VALUE,
                    KEY_END,
                    ATTRIBUTE
                }

                enum eValueState
                {
                    Unknown,
                    String,
                    Object,
                    ObjectEnded,
                    Skipper
                }

                string _json = "";

                public ObjectDeserializatorFromString(string json)
                {
                    _json = json;
                }

                /// <summary>
                /// Represents real instance of the deserializing class
                /// Must be setup from outside after class instantiation
                /// </summary>
                public object Obj = null;
                public ObjectDefinition Od = null;
                public SerializationSettings SerializationSettings = new SerializationSettings();


                public int Start(int startFrom,string caller)
                {
                    try
                    {
                        //NULL SUPPORT, string must support null as value, so 'keyName'; or 'keyName' : ;  - (without value '', but with ;) means null
                        //SerializationSettings ss = new SerializationSettings();
                        //ss.DateTimeFormat = SerializationSettings.eDateTimeFormat.Universal;

                        eState state = eState.BEGIN;

                        //if (Od != null && Od.Descriptor == ObjectDefinition.eDescriptor.SERIALIZED_WITHOUT_KEY)
                        //    state = eState.KEY_END;

                        StringBuilder sbKey = new StringBuilder();
                        StringBuilder sbValue = new StringBuilder();
                        StringBuilder sbAttribute = new StringBuilder();
                        bool useNextSymbolAsChar = false;
                        //If value is not a string but internal object {'':''; '':; 'key':[attribute]:'value'; '':nullable;}
                        eValueState valueState = eValueState.Unknown;
                        char c = ' ';
                        ObjectDeserializatorFromString op = null;
                        PropertyDefinition internalPropertyDefinition = null;

                        //Iterations
                        int[] ri = null;
                        //UpperBounds
                        int[] rub = null;
                        int shift = 0;
                        //Switcher for Array-Like types (those without key)
                        bool valueIsFinished = true;

                        //Debug.WriteLine("-------------  START --------------");

                        for (int i = startFrom; i < _json.Length; i++)
                        {
                            c = _json[i];

                            //Debug.WriteLine(String.Format("{2}  {0}  {1}   [Caller - {3}] ",
                            //    c,
                            //    (Obj == null) ? "null" : Od.Description.ToString(),
                            //    i,
                            //    caller
                            //    ));

                            switch (state)
                            {
                                case eState.BEGIN:
                                    //if (c == '\'')
                                    if (c == SerializationSettings.Framing)
                                    {
                                        // MSB.Append(c);
                                        if (Od != null && Od.Descriptor == ObjectDefinition.eDescriptor.SERIALIZED_WITHOUT_KEY)
                                            state = eState.VALUE;
                                        else
                                            state = eState.KEY;
                                    }
                                    else if (c == '[')
                                    {
                                        state = eState.ATTRIBUTE;
                                    }
                                    else if (c == '{')   //Start of an object
                                    {                                       
                                        continue;
                                    }
                                    else if (c == '}') //End of object
                                    {                                     
                                        return i;
                                    }
                                    //else if (c == ';')
                                    else if (c == SerializationSettings.Delimiter)
                                    {
                                        if (!valueIsFinished)
                                        {
                                            valueIsFinished = true; //This sign belongs to the end of last value
                                            continue;
                                        }
                                        if (Od != null && Od.Descriptor == ObjectDefinition.eDescriptor.SERIALIZED_WITHOUT_KEY)
                                        {
                                            //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> NULLABLE VALUE WITHOUT KEY
                                            //Array like type with nullable element

                                            switch (Od.Description)
                                            {
                                                case ObjectDefinition.eObjectDescription.List:
                                                    Od.List_AddElement(Obj, null);
                                                    break;
                                                case ObjectDefinition.eObjectDescription.HashSet:
                                                    Od.HashSet_AddElement(Obj, null
                                                        );
                                                    break;
                                                case ObjectDefinition.eObjectDescription.Array:
                                                  
                                                    switch (((Array)Obj).Rank)
                                                    {
                                                        case 1:
                                                            ExpressionBasedReflection.GetValueSetterForArray1D()(Obj, ri[0], null);
                                                            break;
                                                        case 2:
                                                            ExpressionBasedReflection.GetValueSetterForArray2D()(Obj, ri[0], ri[1], null);
                                                            break;
                                                        case 3:
                                                            ExpressionBasedReflection.GetValueSetterForArray3D()(Obj, ri[0], ri[1], ri[2], null);
                                                            break;
                                                    }

                                                    ri[ri.Length - 1] = ri[ri.Length - 1] + 1;
                                                    break;
                                            }
                                        }
                                    }
                                    continue;
                                case eState.KEY:
                                    if (useNextSymbolAsChar)
                                    {
                                        sbKey.Append(c);
                                        useNextSymbolAsChar = false;                                       
                                        continue;
                                    }
                                    //if (c == '\'')
                                    if (c == SerializationSettings.Framing)
                                    {
                                        state = eState.KEY_END;                                        
                                    }
                                    else if (c == '\\')
                                    {
                                        useNextSymbolAsChar = true;                                       
                                    }
                                    else
                                    {
                                        sbKey.Append(c);                                        
                                    }
                                    continue;
                                case eState.ATTRIBUTE:
                                    if (useNextSymbolAsChar)
                                    {
                                        sbAttribute.Append(c);
                                        useNextSymbolAsChar = false;                                       
                                        continue;
                                    }
                                    if (c == ']')
                                        state = eState.KEY_END; //End of attribute, that repeats KEY_END state
                                    else if (c == '\\')
                                        useNextSymbolAsChar = true;
                                    else
                                        sbAttribute.Append(c);
                                    continue;
                                case eState.KEY_END:
                                    //if (c == '\'')
                                    if (c == SerializationSettings.Framing)
                                    {
                                        state = eState.VALUE;
                                        // MSB.Append(c);
                                    }
                                    else if (c == '[')
                                    {
                                        state = eState.ATTRIBUTE;
                                        sbAttribute.Clear();
                                    }
                                    //else if (c == ';')
                                    else if (c == SerializationSettings.Delimiter)
                                    {
                                        //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>  NULLABLE VALUE after key
                                        
                                        valueState = eValueState.Unknown;
                                        state = eState.BEGIN;                                        

                                        switch (Od.Description)
                                        {
                                            case ObjectDefinition.eObjectDescription.CustomClass:
                                                internalPropertyDefinition = Od.GetPropertyDefinitionByName(sbKey.ToString());
                                                if (internalPropertyDefinition != null)
                                                {
                                                    internalPropertyDefinition.Setter(Obj, null);                                                   
                                                }
                                                break;
                                            case ObjectDefinition.eObjectDescription.Dictionary:
                                                Od.Dictionary_AddElement(Obj,
                                                    ConvertFromStringToObject(Od.GenericArgument1, sbKey.ToString(), Od.keyTypeFromStringConverter, SerializationSettings),
                                                    null);
                                                break;
                                            case ObjectDefinition.eObjectDescription.List:
                                                Od.List_AddElement(Obj, null);
                                                break;
                                            case ObjectDefinition.eObjectDescription.HashSet:
                                                Od.HashSet_AddElement(Obj, null
                                                    );
                                                break;
                                            case ObjectDefinition.eObjectDescription.Array:                                                
                                                switch (((Array)Obj).Rank)
                                                {
                                                    case 1:
                                                        ExpressionBasedReflection.GetValueSetterForArray1D()(Obj, ri[0], null);
                                                        break;
                                                    case 2:
                                                        ExpressionBasedReflection.GetValueSetterForArray2D()(Obj, ri[0], ri[1], null);
                                                        break;
                                                    case 3:
                                                        ExpressionBasedReflection.GetValueSetterForArray3D()(Obj, ri[0], ri[1], ri[2], null);
                                                        break;
                                                }
                                                ri[ri.Length - 1] = ri[ri.Length - 1] + 1;
                                                break;
                                            default:
                                                throw new Exception("Unknown nullable eObjectDescription");
                                        }

                                        sbKey.Clear();
                                        sbValue.Clear();
                                        // MSB.Append(c);
                                    }
                                    continue;
                                case eState.VALUE:
                                    if (useNextSymbolAsChar)
                                    {
                                        sbValue.Append(c);
                                        useNextSymbolAsChar = false;
                                        // MSB.Append(c);
                                        continue;
                                    }

                                    valueIsFinished = false;
                                    
                                    if (c == '{')
                                    {
                                        //Value contains object
                                        sbValue.Clear();
                                        valueState = eValueState.Object;
                                        //Calling new object parser from this place
                                        op = new ObjectDeserializatorFromString(this._json);
                                        op.SerializationSettings = SerializationSettings;
                                        //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>   START OF INTERNAL OBJECT, recursive call                                 

                                        switch (Od.Description)
                                        {
                                            case ObjectDefinition.eObjectDescription.CustomClass:

                                                internalPropertyDefinition = Od.GetPropertyDefinitionByName(sbKey.ToString());

                                                //Checking if object has such property which is supplied in JSON format by name
                                                if (internalPropertyDefinition != null)
                                                {
                                                    op.Od = ObjectsFactory.GetObjectDefinition(internalPropertyDefinition.Type);
                                                    op.Od.Attribute = sbAttribute.ToString();

                                                    //With Attributes we can create Array right here (with all dimensions)

                                                    if (op.Od.Description == ObjectDefinition.eObjectDescription.Array)
                                                        op.Obj = ObjectsFactory.GetArrayInstance(sbAttribute.ToString(), op.Od.GenericArgument1);
                                                    else
                                                        op.Obj = op.Od.InstanceCreator();

                                                    i = op.Start(i, "start internal object CustomClass");

                                                }

                                                //i = op.Start(i, "start internal object CustomClass");


                                                if (internalPropertyDefinition != null)
                                                {
                                                    //value for the property came from the recursive call, so came object is an object which we just set up
                                                    if (internalPropertyDefinition.Setter != null)
                                                        internalPropertyDefinition.Setter(Obj, op.Obj);
                                                }

                                                break;
                                            case ObjectDefinition.eObjectDescription.Dictionary:

                                                //Key must be of primitive type                                          

                                                if (Od.valueTypeFromStringConverter == null)
                                                {
                                                    //Value is complex and needs extra recursions
                                                    op.Od = ObjectsFactory.GetObjectDefinition(Od.GenericArgument2);
                                                    op.Od.Attribute = sbAttribute.ToString();

                                                    if (op.Od.Description == ObjectDefinition.eObjectDescription.Array)
                                                        op.Obj = ObjectsFactory.GetArrayInstance(sbAttribute.ToString(), op.Od.GenericArgument1);
                                                    else
                                                        op.Obj = op.Od.InstanceCreator();

                                                    //Starting recursion
                                                    i = op.Start(i, "start internal object Dictionary");
                                                }
                                                                                             

                                                if (Od.valueTypeFromStringConverter == null)
                                                {
                                                    //value for the Dictionary key came from the recursive call
                                                    Od.Dictionary_AddElement(Obj,
                                                        ConvertFromStringToObject(Od.GenericArgument1, sbKey.ToString(), Od.keyTypeFromStringConverter, SerializationSettings),
                                                        op.Obj);
                                                }
                                                else
                                                {
                                                    Od.Dictionary_AddElement(Obj,
                                                       ConvertFromStringToObject(Od.GenericArgument1, sbKey.ToString(), Od.keyTypeFromStringConverter, SerializationSettings),
                                                       ConvertFromStringToObject(Od.GenericArgument2, sbValue.ToString(), Od.valueTypeFromStringConverter, SerializationSettings)
                                                       );
                                                }
                                                break;
                                            case ObjectDefinition.eObjectDescription.List:

                                                //Key must be of primitive type                                          

                                                if (Od.valueTypeFromStringConverter == null)
                                                {
                                                    //Value is complex
                                                    op.Od = ObjectsFactory.GetObjectDefinition(Od.GenericArgument1);
                                                    op.Od.Attribute = sbAttribute.ToString();

                                                    if (op.Od.Description == ObjectDefinition.eObjectDescription.Array)
                                                        op.Obj = ObjectsFactory.GetArrayInstance(sbAttribute.ToString(), op.Od.GenericArgument1);
                                                    else
                                                        op.Obj = op.Od.InstanceCreator();

                                                    //Starting recursion
                                                    i = op.Start(i, "start internal object List");
                                                }
                                                                                               

                                                if (Od.valueTypeFromStringConverter == null)
                                                {
                                                    //value for the Dictionary key came from the recursive call
                                                    Od.List_AddElement(Obj, op.Obj);
                                                }
                                                else
                                                {

                                                    Od.List_AddElement(
                                                        Obj,
                                                        ConvertFromStringToObject(Od.GenericArgument1, sbValue.ToString(), Od.valueTypeFromStringConverter, SerializationSettings)
                                                        );
                                                }
                                                break;
                                            case ObjectDefinition.eObjectDescription.HashSet:

                                                //Key must be of primitive type                                          

                                                if (Od.valueTypeFromStringConverter == null)
                                                {
                                                    //Value is complex
                                                    op.Od = ObjectsFactory.GetObjectDefinition(Od.GenericArgument1);
                                                    op.Od.Attribute = sbAttribute.ToString();

                                                    if (op.Od.Description == ObjectDefinition.eObjectDescription.Array)
                                                        op.Obj = ObjectsFactory.GetArrayInstance(sbAttribute.ToString(), op.Od.GenericArgument1);
                                                    else
                                                        op.Obj = op.Od.InstanceCreator();

                                                    //Starting recursion
                                                    i = op.Start(i, "start internal object HashSet");
                                                }
                                                                                             

                                                if (Od.valueTypeFromStringConverter == null)
                                                {
                                                    //value for the Dictionary key came from the recursive call
                                                    Od.HashSet_AddElement(Obj, op.Obj);
                                                }
                                                else
                                                {
                                                    Od.HashSet_AddElement(
                                                        Obj,
                                                        ConvertFromStringToObject(Od.GenericArgument1, sbValue.ToString(), Od.valueTypeFromStringConverter, SerializationSettings)
                                                        );
                                                }
                                                break;
                                            case ObjectDefinition.eObjectDescription.Array:

                                                if (Od.valueTypeFromStringConverter == null)
                                                {
                                                    //Value is complex
                                                    op.Od = ObjectsFactory.GetObjectDefinition(Od.GenericArgument1);
                                                    op.Od.Attribute = sbAttribute.ToString();

                                                    if (op.Od.Description == ObjectDefinition.eObjectDescription.Array)
                                                        op.Obj = ObjectsFactory.GetArrayInstance(sbAttribute.ToString(), op.Od.GenericArgument1);
                                                    else
                                                        op.Obj = op.Od.InstanceCreator();

                                                    //Starting recursion
                                                    i = op.Start(i, "start internal object Array");

                                                }
                                                                                              

                                                #region INSERTING INTO ARRAY
                                                //Iterations
                                                if (ri == null)
                                                {
                                                    string[] dims = Od.Attribute.Split(' ');
                                                    //Iterators
                                                    ri = new int[dims.Length];
                                                    rub = new int[dims.Length];
                                                    //UpperBounds 
                                                    int dim = 0;
                                                    foreach (var dm in dims)
                                                    {
                                                        rub[dim] = Convert.ToInt32(dm);
                                                        dim++;
                                                    }
                                                    shift = 0;
                                                }

                                                if (ri[ri.Length - 1] > rub[ri.Length - 1])
                                                {
                                                    //We must must iterationaly move all others
                                                    ri[ri.Length - 1] = 0;
                                                    shift = ri.Length - 1 - 1;
                                                    while (true)
                                                    {
                                                        if (shift < 0)
                                                            break;
                                                        ri[shift]++;
                                                        if (ri[shift] > rub[shift])
                                                        {
                                                            ri[shift] = 0;
                                                            shift--;

                                                        }
                                                        else
                                                        {
                                                            //Writing value
                                                            switch (ri.Length)
                                                            {
                                                                case 1:
                                                                    if (Od.valueTypeFromStringConverter == null)
                                                                        ExpressionBasedReflection.GetValueSetterForArray1D()(Obj, ri[0], op.Obj);
                                                                    else
                                                                        ExpressionBasedReflection.GetValueSetterForArray1D()(Obj, ri[0], ConvertFromStringToObject(Od.GenericArgument1, sbValue.ToString(), Od.valueTypeFromStringConverter, SerializationSettings));
                                                                    break;
                                                                case 2:
                                                                    if (Od.valueTypeFromStringConverter == null)
                                                                        ExpressionBasedReflection.GetValueSetterForArray2D()(Obj, ri[0], ri[1], op.Obj);
                                                                    else
                                                                        ExpressionBasedReflection.GetValueSetterForArray2D()(Obj, ri[0], ri[1], ConvertFromStringToObject(Od.GenericArgument1, sbValue.ToString(), Od.valueTypeFromStringConverter, SerializationSettings));
                                                                    break;
                                                                case 3:
                                                                    if (Od.valueTypeFromStringConverter == null)
                                                                        ExpressionBasedReflection.GetValueSetterForArray3D()(Obj, ri[0], ri[1], ri[2], op.Obj);
                                                                    else
                                                                        ExpressionBasedReflection.GetValueSetterForArray3D()(Obj, ri[0], ri[1], ri[2], ConvertFromStringToObject(Od.GenericArgument1, sbValue.ToString(), Od.valueTypeFromStringConverter, SerializationSettings));
                                                                    break;
                                                            }

                                                            break;
                                                        }
                                                    }
                                                }
                                                else
                                                {
                                                    //Writing value
                                                    switch (ri.Length)
                                                    {
                                                        case 1:
                                                            if (Od.valueTypeFromStringConverter == null)
                                                                ExpressionBasedReflection.GetValueSetterForArray1D()(Obj, ri[0], op.Obj);
                                                            else
                                                                ExpressionBasedReflection.GetValueSetterForArray1D()(Obj, ri[0], ConvertFromStringToObject(Od.GenericArgument1, sbValue.ToString(), Od.valueTypeFromStringConverter, SerializationSettings));
                                                            break;
                                                        case 2:
                                                            if (Od.valueTypeFromStringConverter == null)
                                                                ExpressionBasedReflection.GetValueSetterForArray2D()(Obj, ri[0], ri[1], op.Obj);
                                                            else
                                                                ExpressionBasedReflection.GetValueSetterForArray2D()(Obj, ri[0], ri[1], ConvertFromStringToObject(Od.GenericArgument1, sbValue.ToString(), Od.valueTypeFromStringConverter, SerializationSettings));
                                                            break;
                                                        case 3:
                                                            if (Od.valueTypeFromStringConverter == null)
                                                                ExpressionBasedReflection.GetValueSetterForArray3D()(Obj, ri[0], ri[1], ri[2], op.Obj);
                                                            else
                                                                ExpressionBasedReflection.GetValueSetterForArray3D()(Obj, ri[0], ri[1], ri[2], ConvertFromStringToObject(Od.GenericArgument1, sbValue.ToString(), Od.valueTypeFromStringConverter, SerializationSettings));
                                                            break;
                                                    }


                                                }

                                                ri[ri.Length - 1] = ri[ri.Length - 1] + 1;
                                                #endregion

                                                break;
                                        }

                                        continue;
                                    }
                                    else if (valueState == eValueState.Object)
                                    {
                                        //if (c == '\'')
                                        if (c == SerializationSettings.Framing)
                                        {
                                            //ObjectValue ends here
                                            valueState = eValueState.Unknown;
                                            state = eState.BEGIN;                                            
                                          
                                            sbKey.Clear();
                                            sbValue.Clear();
                                            
                                        }
                                    }
                                    else
                                    {
                                        valueState = eValueState.String;

                                      
                                        //if (c == '\'')
                                        if (c == SerializationSettings.Framing)
                                        {
                                            //StringValue ends here
                                            valueState = eValueState.Unknown;
                                            state = eState.BEGIN;                                           

                                            //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> END OF NON complex VALUE (NOT NULL)

                                            //Setting value to the object's property. Automatic determination if such property belongs to object

                                            switch (Od.Description)
                                            {
                                                case ObjectDefinition.eObjectDescription.CustomClass:
                                                    internalPropertyDefinition = Od.GetPropertyDefinitionByName(sbKey.ToString());
                                                    if (internalPropertyDefinition != null)
                                                    {
                                                        internalPropertyDefinition.Setter(Obj,
                                                           ConvertFromStringToObject(internalPropertyDefinition.Type, sbValue.ToString(), internalPropertyDefinition.TypeConverterFromString, SerializationSettings)
                                                           );
                                                    }
                                                    break;
                                                case ObjectDefinition.eObjectDescription.Dictionary:

                                                    Od.Dictionary_AddElement(Obj,
                                                        ConvertFromStringToObject(Od.GenericArgument1, sbKey.ToString(), Od.keyTypeFromStringConverter, SerializationSettings),
                                                        ConvertFromStringToObject(Od.GenericArgument2, sbValue.ToString(), Od.valueTypeFromStringConverter, SerializationSettings)
                                                        );

                                                    break;
                                                case ObjectDefinition.eObjectDescription.List:

                                                    Od.List_AddElement(Obj,
                                                        ConvertFromStringToObject(Od.GenericArgument1, sbValue.ToString(), Od.valueTypeFromStringConverter, SerializationSettings)
                                                        );

                                                    break;
                                                case ObjectDefinition.eObjectDescription.HashSet:

                                                    Od.HashSet_AddElement(Obj,
                                                        ConvertFromStringToObject(Od.GenericArgument1, sbValue.ToString(), Od.valueTypeFromStringConverter, SerializationSettings)
                                                        );

                                                    break;
                                                case ObjectDefinition.eObjectDescription.Array:
                                                    //Value array
                                                    #region INSERTING INTO ARRAY
                                                    //Iterations
                                                    if (ri == null)
                                                    {
                                                        string[] dims = Od.Attribute.ToString().Split(' ');
                                                        //Iterators
                                                        ri = new int[dims.Length];
                                                        rub = new int[dims.Length];
                                                        //UpperBounds 
                                                        int dim = 0;
                                                        foreach (var dm in dims)
                                                        {
                                                            rub[dim] = Convert.ToInt32(dm);
                                                            dim++;
                                                        }
                                                        shift = 0;
                                                    }

                                                    if (ri[ri.Length - 1] > rub[ri.Length - 1])
                                                    {
                                                        //We must must iterationaly move all others
                                                        ri[ri.Length - 1] = 0;
                                                        shift = ri.Length - 1 - 1;
                                                        while (true)
                                                        {
                                                            if (shift < 0)
                                                                break;
                                                            ri[shift]++;
                                                            if (ri[shift] > rub[shift])
                                                            {
                                                                ri[shift] = 0;
                                                                shift--;

                                                            }
                                                            else
                                                            {
                                                                //Writing value
                                                                switch (ri.Length)
                                                                {
                                                                    case 1:
                                                                        ExpressionBasedReflection.GetValueSetterForArray1D()(Obj, ri[0], ConvertFromStringToObject(Od.GenericArgument1, sbValue.ToString(), Od.valueTypeFromStringConverter, SerializationSettings));
                                                                        break;
                                                                    case 2:
                                                                        ExpressionBasedReflection.GetValueSetterForArray2D()(Obj, ri[0], ri[1], ConvertFromStringToObject(Od.GenericArgument1, sbValue.ToString(), Od.valueTypeFromStringConverter, SerializationSettings));
                                                                        break;
                                                                    case 3:
                                                                        ExpressionBasedReflection.GetValueSetterForArray3D()(Obj, ri[0], ri[1], ri[2], ConvertFromStringToObject(Od.GenericArgument1, sbValue.ToString(), Od.valueTypeFromStringConverter, SerializationSettings));
                                                                        break;
                                                                }

                                                                break;
                                                            }
                                                        }
                                                    }
                                                    else
                                                    {
                                                        //Writing value
                                                        switch (ri.Length)
                                                        {
                                                            case 1:
                                                                ExpressionBasedReflection.GetValueSetterForArray1D()(Obj, ri[0], ConvertFromStringToObject(Od.GenericArgument1, sbValue.ToString(), Od.valueTypeFromStringConverter, SerializationSettings));
                                                                break;
                                                            case 2:
                                                                ExpressionBasedReflection.GetValueSetterForArray2D()(Obj, ri[0], ri[1], ConvertFromStringToObject(Od.GenericArgument1, sbValue.ToString(), Od.valueTypeFromStringConverter, SerializationSettings));
                                                                break;
                                                            case 3:
                                                                ExpressionBasedReflection.GetValueSetterForArray3D()(Obj, ri[0], ri[1], ri[2], ConvertFromStringToObject(Od.GenericArgument1, sbValue.ToString(), Od.valueTypeFromStringConverter, SerializationSettings));
                                                                break;
                                                        }
                                                    }

                                                    ri[ri.Length - 1] = ri[ri.Length - 1] + 1;

                                                    #endregion

                                                    break;
                                                default:
                                                    throw new Exception("Unknown eObjectDescription");
                                            }


                                            sbKey.Clear();
                                            sbValue.Clear();                                           
                                        }
                                        else if (c == '\\')
                                        {
                                            useNextSymbolAsChar = true;

                                            if (valueState == eValueState.Unknown)
                                                valueState = eValueState.String;
                                        }
                                        else
                                        {
                                            sbValue.Append(c);                                            
                                        }
                                    }

                                    continue;
                            }
                        }


                        return this._json.Length;

                    }
                    catch (Exception ex)
                    {
                        //StackTrace st = new StackTrace(ex, true);
                        //StackFrame frame = st.GetFrame(0);
                        //int line = frame.GetFileLineNumber();
                        throw ex;
                    }


                }//Eo Start

            }//eoc


            /// <summary>
            /// 
            /// </summary>
            /// <param name="t"></param>
            /// <param name="value"></param>
            /// <param name="converter"></param>
            /// <param name="settings"></param>
            /// <returns></returns>
            static object ConvertFromStringToObject(Type t, string value, Func<string, Tuple<bool, object>> converter, SerializationSettings settings)
            {
                if (converter == null)  //Normally it should happen in case of one exception (Array first tells its dimension bounds, but can be of other data type)
                    return value;

                if (t == TypeDateTime || t == TypeDateTime_NULL)        //Very specific converter, moved out from the others, because used dynamic parameters SerializationSettings, the rest are precompiled
                {
                    DateTime dt = DateTime.Now;
                    //DateTime value is never empty here
                    switch (settings.DateTimeFormat)
                    {
                        case SerializationSettings.eDateTimeFormat.EuropeanVisual:      //Will convert from string to Local Time. UTC+- will be always local Otherwise UTC. +- is given for WinterTime (Hamburg +1)
                            //          1         2
                            //012345678901234567890123
                            //dd.MM.yyyy HH:mm:ss UTC+0
                            dt = DateTime.ParseExact(value.Substring(0, 19), "dd.MM.yyyy HH:mm:ss", System.Globalization.CultureInfo.InvariantCulture);

                            if (value.Length > 19)  //If UTC then always tries convert to LocalTime
                            {
                                dt = dt.AddHours(
                                    Convert.ToInt32(value.Substring(23))
                                    );
                                if (DateTime.Now.IsDaylightSavingTime())
                                    dt = dt.AddHours(-1);    //Bring from summer to winder

                                dt = DateTime.SpecifyKind(dt, DateTimeKind.Local);
                            }
                            else
                                dt = DateTime.SpecifyKind(dt, DateTimeKind.Utc);

                            break;
                        case SerializationSettings.eDateTimeFormat.Ticks:
                            dt = new DateTime(Convert.ToInt64(value), DateTimeKind.Utc);
                            break;
                        case SerializationSettings.eDateTimeFormat.Universal:
                            //          1         2
                            //012345678901234567890123
                            //yyyyMMdd HHmmss+0
                            dt = DateTime.ParseExact(value.Substring(0, 14), "yyyyMMddHHmmss", System.Globalization.CultureInfo.InvariantCulture);

                            if (value.Length > 14)  //If UTC then always tries convert to LocalTime
                            {
                                dt = dt.AddHours(
                                    Convert.ToInt32(value.Substring(14))
                                    );
                                if (DateTime.Now.IsDaylightSavingTime())
                                    dt = dt.AddHours(-1);    //Bring from summer to winter

                                dt = DateTime.SpecifyKind(dt, DateTimeKind.Local);
                            }
                            else
                                dt = DateTime.SpecifyKind(dt, DateTimeKind.Utc);

                            break;
                    }
                    return dt;
                }



                var res = converter(value);

                if (t == CharType)
                {
                    return Convert.ToChar(res.Item2);
                }
                else
                {
                    return res.Item2;
                }


            }
        }

       




        /// <summary>
        /// Formats SernaString for the better human reads.
        /// </summary>
        /// <param name="s"></param>
        /// <returns></returns>
        public static string BeautifyJSONString(this string s)
        {
            StringBuilder sb = new StringBuilder();
            int horizontal = 0;

            int[] horizonts = new int[10000];
            int hrIndex = -1;
            bool CarriageReturn = false;

            foreach (var c in s)
            {
                switch (c)
                {                    
                    case '{':
                    case '[':
                        sb.Append(c);
                        hrIndex++;
                        horizonts[hrIndex] = horizontal;
                        CarriageReturn = true;
                        break;
                    case '}':
                    case ']':
                        CarriageReturn = false;
                        sb.Append('\n');
                        horizontal = horizonts[hrIndex];
                        hrIndex--;
                        for (int i = 0; i < horizontal; i++)
                        {
                            sb.Append(' ');
                        }
                        sb.Append(c);
                        break;
                    case ',':
                        sb.Append(c);
                        CarriageReturn = true;

                        break;
                    default:
                        if (CarriageReturn)
                        {
                            CarriageReturn = false;
                            sb.Append('\n');
                            horizontal = horizonts[hrIndex] + 1;
                            for (int i = 0; i < horizontal; i++)
                            {
                                sb.Append(' ');
                            }
                        }
                        sb.Append(c);
                        break;
                }

                horizontal++;
            }

            return sb.ToString();
        }

        /// <summary>
        /// Formats SernaString for the better human reads.
        /// </summary>
        /// <param name="s"></param>
        /// <returns></returns>
        public static string BeautifySernaString(this string s)
        {
            StringBuilder sb = new StringBuilder();
            int horizontal = 0;

            int[] horizonts = new int[10000];
            int hrIndex = -1;
            bool CarriageReturn = false;

            foreach (var c in s)
            {
                switch (c)
                {
                    case '{':
                        sb.Append(c);
                        hrIndex++;
                        horizonts[hrIndex] = horizontal;
                        CarriageReturn = true;                      
                        break;
                    case '}':
                        CarriageReturn = false;
                        sb.Append('\n');
                        horizontal = horizonts[hrIndex];
                        hrIndex--;
                        for (int i = 0; i < horizontal; i++)
                        {
                            sb.Append(' ');
                        }
                        sb.Append(c);
                        break;
                    case ';':
                        sb.Append(c);
                        CarriageReturn = true;

                        break;
                    default:
                        if (CarriageReturn)
                        {
                            CarriageReturn = false;
                            sb.Append('\n');
                            horizontal = horizonts[hrIndex] + 1;
                            for (int i = 0; i < horizontal; i++)
                            {
                                sb.Append(' ');
                            }
                        }
                        sb.Append(c);
                        break;
                }

                horizontal++;
            }

            return sb.ToString();
        }


    }
}
